﻿using System;
using System.Collections.Generic;
using System.Linq;
using Simp.Model;
using Simp.Model.CoreExpressions;
using Simp.Model.CoreTransformations;
using Simp.Model.Exceptions;

namespace Simp.Model
{
	/// <summary>Evaluation context accumulating assignations and able to evaluate expressions using those variables.</summary>
	public class EvaluationContext
	{
		private readonly IDictionary<string, IExpression> _expressionIndex = new Dictionary<string, IExpression>();

		/// <summary>Private Constructor.</summary>
		private EvaluationContext() { }

		/// <summary>Loads an <see cref="EvaluationContext"/> with no known expressions.</summary>
		/// <returns></returns>
		public static EvaluationContext WithNoKnownExpressions()
		{
			return new EvaluationContext();
		}

		/// <summary>Loads an <see cref="EvaluationContext"/> with core expressions.</summary>
		/// <returns></returns>
		public static EvaluationContext WithCorePackage()
		{
			EvaluationContext context = new EvaluationContext();

			context.AddKnownExpressions(CorePackage.Package);

			return context;
		}
		/// <summary>List of currently known expressions.</summary>
		public IExpression[] KnownExpressions
		{
			get { return _expressionIndex.Values.ToArray(); }
		}

		/// <summary>Adds known expressions.</summary>
		/// <param name="expressionList"></param>
		public void AddKnownExpressions(params IExpression[] expressionList)
		{
			foreach (IExpression expression in expressionList)
			{
				INamedExpression namedExpression = ExpressionHelper.GetProxy(expression);

				_expressionIndex.Add(namedExpression.Name, expression);
			}
		}

		/// <summary>Integrate an expression in the context.</summary>
		/// <param name="expression"></param>
		/// <returns>Returns the integrated expression.</returns>
		public IExpression Integrate(IExpression expression)
		{
			if (expression == null)
			{
				throw new ArgumentNullException("expression");
			}

			IExpressionTransformation unscopeTransformation = new UnscopeTransformation();
			IExpressionTransformation bindingTransformation = new ExpressionBindingTransformation(false, _expressionIndex);
			IExpression transformedExpression =
				ExpressionHelper.Tranform(false, expression, unscopeTransformation, bindingTransformation);
			IExpression simplifiedExpression =
				ExpressionHelper.Simplify(false, transformedExpression, SimplificationMode.PerfectEquivalent);

			if (simplifiedExpression != null)
			{
				CustomExpression custom;
				CompositeExpression composite = ExpressionHelper.TryGetComposite(simplifiedExpression, out custom);

				if (composite != null && !custom.IsLambda)
				{
					_expressionIndex.Add(((INamedExpression)custom).Name, composite);
				}
			}

			return simplifiedExpression;
		}

		/// <summary>Integrate a collection of expressions in the context.</summary>
		/// <param name="expressionList"></param>
		public IExpression[] Integrate(params IExpression[] expressionList)
		{
			IExpression[] integratedExpressions = new IExpression[expressionList.Length];

			for (int i = 0; i != expressionList.Length; ++i)
			{
				integratedExpressions[i] = Integrate(expressionList[i]);
			}

			return integratedExpressions;
		}
	}
}